
local Session = class("Session")

local Connecter = require("app.utils.TCPConnecter")


local json = require("quick.json")
local FileUtils = cc.FileUtils:getInstance()
local executeFunc = _G["executeFunc"]

local function parseFullVersion(strFullVersion)
	local ret = {}
	for subVersion in string.gmatch(strFullVersion, "%d+") do
		table.insert(ret, tonumber(subVersion))
	end
	return ret
end

local function versionCompareLess(a, b)
	if #a ~= #b then
		return #a < #b
	end
	local length = #a
	for i = 1, length do
		if a[i] < b[i] then
			return true
		end
	end
	return false
end

Session.Event_BlockingChanged = "BlockingChanged"

function Session:ctor()

	self.connecter = Connecter.new()
	self.connecter.tag = "Session"
	self.zoneId = nil
	self.oldZoneId = nil
	self.zoneName = nil
	self.serverTimeStamp = nil
	self.account = nil
	self.token = nil
	self._host = nil
	self._port = nil

	self._block = false
	self._blockCron = nil

	self._failCallback = nil

	self._queueView = nil
	self._queueDelayCron = nil
	self._appEnterForeground = false
	self._switchNetwork = false
	self.openDays = 0
	self.mergeDays = 0; --合服后的开服时间

	self.loginDays = 0
	self.isKOL = false
	-- 已经验证账号
	self._hasAuth = false
	-- 已经进入游戏（点击了进入游戏，重连逻辑要按进入游戏进行）
	self._hasEnterGame = false
	-- 玩家游戏数据初始化成功
	self._hasUserInitComp = false

	-- 自动重连次数
	self._autoReconnectTimes = 0
	-- 自动重连延时
	self._autoReconnectCronId = nil

	-- 进入游戏的回调
	self._enterGameCB = nil
	-- 重连游戏成功的回调
	self._reconnectGameCB = nil

	self.connecter:on(Connecter.EVENT_CLOSE,handler(self,Session.onConnectNotHealth_))

	-- 切回前台，如果链接断开，1.有相关参数，试着重连 2.没有相关参数，重启app
	EM:on("OnAppEnterForeground", function()
		release_print("OnAppEnterForeground", self.token, self.account, self:isOpen())

		-- 因为当app从后台返回的那一刻 isOpen() == true，TCPConnecter无法及时正确反应socket是否已经被服务端主动关闭
		-- 服务器会控制在一定时间内，socket是保持连接的，例如20秒
		-- 所以增加self:queryServerTime, 假设正常得到callback，那就不需要重联
		self:queryServerTime(function()
			print("OnAppEnterForeground -- queryServerTime -- sucess")
			self._appEnterForeground = false
		end)
		
		print("OnAppEnterForeground -- true")
		self._appEnterForeground = true
		
	end)

	EM:on("onSwitchNetwork",function()
        release_print("onSwitchNetwork")
        self._switchNetwork = true
    end)

	-- 监听排队，在auth之前收到
	self:on("pb_login.S2C_EntranceQueue", function(msg)
			-- dump(msg, "S2C_EntranceQueue")
			self:_onEnterQueue(msg)
		end)

	self:on("pb_login.S2C_Auth", function(msg)
			-- dump(msg, "S2C_Auth")
			self:_connectSucc(msg)
			self:_onExitQueue()
		end)

	self:on("pb_query.S2C_QueryUser", function(msg)
			self:_onUpdateUser(msg)
		end)


	self:on("pb_map.S2C_BattleEnd",function(msg)
		local userData = UD
		userData.battleId = nil
		userData.battleUrl = nil
		userData.battleAuthCode = nil
		userData.formationId = nil
	end)


end

----------------------- game login ---------------------
function Session:login(url,data,cb)
	assert(app.http)
	assert(url)
	self.loginUrl = url
	app.http.reqJsonData(url,function(decode,errorMsg,code)
		-- dump(decode, "Session:login", 10)
		if code == 200 then
			local param = decode.data
			local msg = decode.msg
			local ret = decode.ret
			if ret == 0 and param then
				local token = param.token
				local account = param.game_account
				self.token = assert(token)
				self.account = assert(account)

				SAFE_SHIELD_ENABLE = param.tss_shield
				SAFE_FILTER_ENABLE = param.tss_filter
				
				release_print("=========>Session:login token account", token, account)
				release_print("=========>SAFE_SHIELD_ENABLE, SAFE_FILTER_ENABLE", 
					SAFE_SHIELD_ENABLE, SAFE_FILTER_ENABLE)
			else
				self.token = nil
				self.account = nil
				release_print("========>Session:login fail ", msg, ret)
			end
		else
			release_print("=========>Session:login fail errorMsg, code", errorMsg, code)
		end
		if cb then
			cb(decode, errorMsg, code)
		end
	end,"POST",true,data)
end

function Session:isLogined()
	return self.token~=nil and self.account~=nil
end

function Session:clearLogined()
	self.token = nil
	self.account = nil
end

----------------------- 使用激活码 ---------------------
function Session:useCode(url, data, cb)
	app.http.reqJsonData(url,function(decode,errorMsg,code)
		if cb then
			cb(decode, errorMsg, code)
		end
	end,"POST",true,data)
end

----------------------- 连接socket ---------------------
function Session:setHosts(hosts)
	assert(hosts)
	self._hosts = hosts
end

function Session:getHosts()
	return self._hosts
end

--set host url like ws://xxxxxxx.com
function Session:setWsUrl(url)
	assert(url)
	self.wsURL = url
	local protocol,host,port,path = string.parseUrl(url)
	self._host = host
	self._port = port
	self._wspath = path
	self._httpUrl = app.http.joinUrl(host,port)
end

-- id 		服务器id
-- mergeId	合服id（存在时，游戏业务逻辑都应该以合服id为准）
function Session:setZoneId(zoneId, mergeId)
	if mergeId and mergeId > 0 then
		self.zoneId = mergeId
		self.oldZoneId = zoneId
	else
		self.zoneId = zoneId
		self.oldZoneId = nil
	end
	assert(self.zoneId)
end

-- 当前服id
function Session:getZoneId()
	return self.zoneId
end

-- 老服id
function Session:getOldZoneId()
	if self.oldZoneId then
		return self.oldZoneId
	else
		return self.zoneId
	end
end

function Session:setZoneName(name)
	self.zoneName = name
end


function Session:handleAuthErrorMsg(err)
	local errMsg = L(err)
	if err == "ErrVerifyFail" or err == "ErrServerShutDown" or err == "ErrServerMaintain" then
		-- 这几个错误，就不需要再重试了
		local messageBox = display.newMessageBox(errMsg)
		UIHandler:addPopDialog(messageBox)
	else
		-- 剩下的错误交给上层业务处理
		return errMsg
	end
end

function Session:_connect(cb)
	assert(self.zoneId)
	local token = assert(self.token)
	local account = assert(self.account)

	self:_setBlock(true)
	self._connectCB = cb

	local openCB = function(code, err)
		release_print("[Session] connect", code, err, self._connectCB)
		if code == 0 then
			-- 身份验证
			local params = {}
			params.account = account
			params.token = token
			params.clientversion = app:getFullVersionString()
			release_print("[Session] auth client version", params.clientversion)
			params.systemsoftware = device.getSystemSoftware() or "none"
			params.systemhardware = device.getSystemHardware() or "none"
			params.telecomoper = device.getTelecomOper() or "none"
			params.network = device.getNetworkType() or "none"
			params.deviceid = app.channelAPI:getDeviceId() or "none"
			params.platform = app.channelAPI:getPlatform()
			params.zoneId = self.zoneId
			-- 支付相关参数
			params.pf = app.channelAPI:getPf() or ""
			params.pfKey = app.channelAPI:getPfKey() or ""
			params.accessToken = app.channelAPI:getAccessToken() or ""
			params.qqPayToken = app.channelAPI:getQqPayToken() or ""

			self._authFailCB = function()
				release_print(">Session pb_login.C2S_Auth connect close")
				executeFunc(self._connectCB, L("network_error"), 0)
				self:clearAuthCB()
			end

			-- dump(params, "pb_login.C2S_Auth params")
			self:request("pb_login.C2S_Auth", params, nil, 
				nil,
				function(errMsg)
					self:_setBlock(false)
					release_print(">Session pb_login.C2S_Auth fail ",errMsg.content)
					local errorMsg = self:handleAuthErrorMsg(errMsg.content)
					if errorMsg then
						executeFunc(self._connectCB, errorMsg, 0)
						self:clearAuthCB()
					end
					return true --swallow events
				end)
		else
			self:_setBlock(false)
			executeFunc(self._connectCB, err or L("lua_code_text_287"), code)
			self:clearAuthCB()
		end
	end

	self.connecter:open(self._host, self._port, openCB, 3, 1000)
end

function Session:clearAuthCB()
	self._connectCB = nil
	self._authFailCB = nil
end

-- 链接成功，并且经过了排队
function Session:_connectSucc(msg)
	release_print("pb_login.S2C_Auth ", msg.ok)
	self:_setBlock(false)

	-- 版本不一致，弹框返回
	if self:setClientVersion(msg.clientversion) then return end

	self._hasAuth = msg.ok
	executeFunc(self._connectCB, nil, 0, msg)
	self:clearAuthCB()
end

-- 链接上soket，并发C2S_Auth
function Session:connect(cb)
	self:_connect(cb)
end

-- 链接上soket，然后发送C2S_Auth，成功之后发根据是否进入游戏发送C2S_EnterGame拉去数据
function Session:reconnect(cb)
	self:_connect(function(err, code)
		if err then
			executeFunc(cb, err, code)
		else
			if self._hasEnterGame then
				-- 进入游戏的重连，需要调用enterGame，重新排队刷新数据
				self:enterGame(self.uid, function(errMsg)
					executeFunc(cb, errMsg, code)
				end, true)
			else
				-- 未进入游戏的重连，不需要调用enterGame
				executeFunc(cb, err, code)
			end
		end
	end)
end

----------------------- 进入游戏／排队 ---------------------
function Session:enterGame(uid, cb, isReconnect)
	isReconnect = isReconnect or false
	self.uid = uid
	app.session:request("pb_login.C2S_EnterGame", {uid=uid}, function(msg, err)
		release_print("pb_login.S2C_EnterGame", msg.ok)
		if msg.ok then
			
		end
    end)

	self:_setBlock(true)
	self._hasEnterGame = true
	if isReconnect then
		self._reconnectGameCB = cb
	else
		self._enterGameCB = cb
	end

	if self._enterGameCB and not self:isOpen() then
		executeFunc(self._enterGameCB, true)
	end
end

function Session:_onEnterQueue(ret)
	-- dump(ret, "pb_login.S2C_EntranceQueue")
	ret.pos = ret.pos or 0
	ret.estimatedWait = ret.estimatedWait or 0
	if ret.pos >= 1 then
		if ret.pos == 1 then
			if not self._queueView then
				-- pos=1，排在第一位延迟一会显示排队界面
				self._queueDelayCron = scheduler.newCron(function()
					self._queueDelayCron = nil
					self:_refreshQueueView(ret)
				end, 1)
			else
				self:_refreshQueueView(ret)
			end
		else
			self:_refreshQueueView(ret)
		end
	end
end

function Session:_refreshQueueView(ret)
	self:_setBlock(false)
	EM:notify("onEnterQueue")
	if not self._queueView then
		local LoginQueueView = require("app.login.views.LoginQueueView")
		self._queueView = LoginQueueView.new()
		app:getRunningScene():addChild(self._queueView, 2)
		self._queueView:onClose(function()
			self._queueView:removeFromParent()
			self._queueView = nil

			self:clearAuthCB()

			self:disconnect(function()
				EM:notify("onCancelQueue")

				-- 进入游戏后触发的排队主动取消，应该重启游戏
				if self._hasEnterGame then
					app:relaunchApp()
				end
			end)
		end)
	end
	self._queueView:setZoneName(self.zoneName or string.format(L("lua_code_text_73"), self.zoneId))
	self._queueView:setQueueIndex(ret.pos)
	self._queueView:setWaitTime(ret.estimatedWait)
end

function Session:_onExitQueue()
	if self._queueDelayCron then
		scheduler.cancelCron(self._queueDelayCron)
		self._queueDelayCron = nil
	end

	if self._queueView then
		self._queueView:removeFromParent()
		self._queueView = nil
	end
end

function Session:_onUpdateUser(msg)
	release_print("pb_query.S2C_QueryUser")
	
	self.connecter:setPingFunc(function(connecter,cb)
		self:queryServerTime(cb)
	end)

	-- clear team
	app:sendMsg("TeamModel", "setTeam")

	-- init server time
	self.serverTimeStamp = msg.timestamp / 1000000000
	self.queryBeginTime = app:getTime()
	self.strtimezone = msg.timezone
	self.timeoffset = msg.timeoffset
	self.timezone = math.floor(msg.timeoffset / 3600)
	self.worldExpLock = msg.worldExpLock --世界经验锁当前所用的ID
	self.openDays = msg.openDays  --开服第几天
	self.mergeDays = msg.mergeDays
	self.loginDays = msg.loginDays --累计登录天数
	self.isKOL = msg.isKOL -- 是否是意见领袖
	app:startDailyCron()

	self:setClientVersion(msg.clientVersion)

	-- init battle
	UD:initBattle(msg)

	local userData = assert(msg.userData)
	UD:read(userData)


    -- login im
	app:sendMsg("IMModel", "login")

	self._hasUserInitComp = true

	executeFunc(self._reconnectGameCB)
	self._reconnectGameCB = nil
	executeFunc(self._enterGameCB)
	self._enterGameCB = nil
	self:_setBlock(false)

	self.openStamp = msg.openStamp
end

-- 客户端版本更新，服务器传过来只为了说明有新版本
function Session:setClientVersion(clientVersion)
	release_print("session setClientVersion current:", app:getFullVersionString(), "new:", clientVersion)

	if clientVersion ~= nil and clientVersion ~= "" and clientVersion ~= "0" then
		local currentVersion = parseFullVersion(app:getFullVersionString())
		local newVersion = parseFullVersion(clientVersion)
		if versionCompareLess(currentVersion, newVersion) then
			if cc.isAlive(self._versionDialog) then
				self._versionDialog:removeFromParent()
			end
			local dialog = display.newMessageBox(L("hotfix_notice_01"), 
			function()
				self._versionDialog = nil

				if app:getPublisher() == Enums.Publisher.ema then
					-- 海外版重启拿不到新的版本号，直接关闭游戏
					app:exit()
				else
					app:relaunchApp()
				end
			end)
			UIHandler:addPopDialog(dialog,1,true)
			self._versionDialog = dialog
			return true
		end
	end
	return false
end

function Session:getVirtualTime()
	if self.openStamp and self.serverTimeStamp then
		local gameStartTime = checknumber(GD:getSystemStrPar("gameStartTime"))
		local hourScale = checknumber(GD:getSystemStrPar("gameHourSystem"))
		local nowStamp = self:getServerTime()

		local sd = math.floor(gameStartTime/10000) * 360 + math.fmod(math.floor(gameStartTime/100), 100) * 30 + math.fmod(gameStartTime, 100)
		local td = math.floor((nowStamp - self.openStamp) / (hourScale*3600)) + sd
		local ret = {}

		ret.year = math.floor(td/360)
		ret.month = math.floor(math.fmod(td, 360) / 30)
		ret.day = math.fmod(td, 30)

		return ret
	end
end

----------------------- 各种接口 ---------------------
function Session:disconnect(cb, isForce)
	self.connecter:close(cb, isForce)
end

function Session:setKeepAlive(b)
	self.connecter.keepalive = b
end

function Session:isConnecting()
	return self.connecter:isConnecting()
end

function Session:isClosing()
    return self.connecter:isClosing()
end

function Session:isOpen()
	return self.connecter:isOpen()
end

function Session:isBlocked()
	return self._block
end

-- 获取信号格数，分三格，越大信号越好，没连接是0
function Session:getSignalLevel()
	if self.connecter:isOpen() then
		local ms = self:getDelayTimeMs()
		if ms <= 200 then
			return 3
		elseif ms > 200 and ms < 500 then
			return 2
		else
			return 1
		end
	else
		return 0
	end
end

function Session:getDelayTimeMs()
    return self.connecter:getDelayTimeMs()
end

function Session:getZoneDBFileName()
	return "zoneDB_"..self.zoneId
end

-- self._hasUserInitComp 之前可以发出的请求
local LOGIN_REQ_MSG_ENABLE_DICT = {
	["pb_login.C2S_Auth"] = true,
	["pb_login.C2S_CreateRole"] = true,
	["pb_login.C2S_DeleteRole"] = true,
	["pb_login.C2S_EnterGame"] = true,
}
-- self._hasUserInitComp 之前可以接受的消息
local LOGIN_RECV_MSG_ENABLE_DICT = {
	["pb_login.S2C_Auth"] = true,
	["pb_login.S2C_CreateRole"] = true,
	["pb_login.S2C_DeleteRole"] = true,
	["pb_login.S2C_EnterGame"] = true,
	["pb_login.S2C_EntranceQueue"] = true,
	["pb_query.S2C_QueryUser"] = true,
	["shared.ServerMessage"] = true,
	["pb_server.S2C_ServerInfo"] = true,
}

-- @param message 	请求消息
-- @param data 		请求数据
-- @param cb 		请求成功回调
-- @param opts 		请求参数{block=是否转菊花, failedCallback=网络错误回调}
-- @param errcb 	请求失败回调（实际是shared.ServerMessage消息）
-- block 转菊花只在消息有回包时使用
function Session:request(message,data,cb,opts,errcb)
	if not self._hasAuth and message ~= "pb_login.C2S_Auth" then
		release_print("session can't request before auth", message)
		return
	end
	
	if not self._hasUserInitComp and not LOGIN_REQ_MSG_ENABLE_DICT[message] then
		release_print("session can't request before enterGame", message)
		return
	end
	
	if not cb and not errcb then
		self:notify(message,data)
		return
	end

	local opts = opts or {}
	if opts.block then
		self:_setBlock(true)
	end

	if opts.failedCallback then
		self._failedCallback = opts.failedCallback
	end

	return self.connecter:send(message,data,function(msg,err)
		if opts.block then
			self:_setBlock(false)
		end
		self._failedCallback = nil
		if type(err)=="nil" then
			return executeFunc(cb, msg)
		else
			return executeFunc(errcb, err)
		end
	end,opts.node)
end

function Session:removeRequestCBById(id)
    self.connecter:removeRequestCBById(id)
end

function Session:removeRequestCBByName(name)
    self.connecter:removeRequestCBByName(name)
end

function Session:tick()
	self.connecter:tick()
end

function Session:notify(message,data)
	self.connecter:notify(message,data)
end

function Session:on(message,cb)
	self.connecter:on(message,function(...)
		-- if not self._hasAuth and message ~= "pb_login.S2C_Auth" then
		-- 	release_print("session can't receive before auth", message)
		-- 	return
		-- end
		if not self._hasUserInitComp and not LOGIN_RECV_MSG_ENABLE_DICT[message] then
			release_print("session can't receive before enterGame", message)
			return
		end
		executeFunc(cb, ...)
	end)
end

function Session:off(message)
	self.connecter:off(message)
end

----------------------- 重连／自动重连 ---------------------
function Session:showReconnectDialog()
	local reconnectDialog = self._reconnectDialog
	if cc.isDead(reconnectDialog) then
		reconnectDialog = require("app.widget.ReconnectDialog").new(
			L("tips_1000003"),
			function()
				self:reconnectAuto(true)
			end,
			function()
				self._reconnectDialog=nil
				app:relaunchApp()
			end)

		reconnectDialog:setConfirmText(L("tips_1000005"))
		reconnectDialog:setCancelText(L("tips_1000004"))
		UIHandler:addPopDialog(reconnectDialog,1,true)
		self._reconnectDialog=reconnectDialog
	end
	reconnectDialog:refreshConfirmBtn()
end

function Session:removeReconnectDialog()
	local reconnectDialog = self._reconnectDialog
	if cc.isAlive(reconnectDialog) then
		reconnectDialog:removeFromParent()
	end
	self._reconnectDialog = nil
end

function Session:clearReconnectCron()
	if self._autoReconnectCronId then
		scheduler.cancelCron(self._autoReconnectCronId)
		self._autoReconnectCronId = nil
	end
end

function Session:checkReconnctAuto()
	self:clearReconnectCron()

	self._autoReconnectTimes = self._autoReconnectTimes + 1
	release_print("[Session] _autoReconnectTimes:", self._autoReconnectTimes)
	if self._autoReconnectTimes <= 3 then
		-- 延迟2s自动重连
		self._autoReconnectCronId = scheduler.newCron(function()
				self:reconnectAuto()
			end, 2)
	else
		self._autoReconnectTimes = 0
		self:showReconnectDialog()
	end
end

-- 自动重连一次，不显示弹框，不成功之后再显示确认弹框
-- ignoreAuto 忽略重连
function Session:reconnectAuto(ignoreAuto)
	-- 这里处理一下 在切换账号过程中 会先清除 token 这个过程触发 断线重连 有个lua报错
	if self.token == nil then return end

	self:reconnect(function(error,code)
		release_print("[Session] reconnect error:", error, code)
		if not error then
			self:clearReconnectCron()
			self._autoReconnectTimes = 0
			self:removeReconnectDialog()

			-- 进入游戏的重连才发送 onReconnectSuccessed 消息
			if self._hasEnterGame then
				EM:notify("onReconnectSuccessed")
			end
		else
			if ignoreAuto then
				self:showReconnectDialog()
			else
				-- 连接失败，按照重连次数决定是否重连
				self:checkReconnctAuto()
			end
		end
	end)
end

-- 连接断开
function Session:onConnectNotHealth_(event)
	-- dump(event, "Session:onConnectNotHealth_")
	release_print("Session:onConnectNotHealth_", self._appEnterForeground, self._switchNetwork, event.forceClosing, self._hasEnterGame)

	self._hasUserInitComp = false

	if self._enterGameCB then
		executeFunc(self._enterGameCB, true)
	end

	-- 连接断开，退出排队
	self:_onExitQueue()

	if self._failedCallback then
		self._failedCallback()
	end
	self:_setBlock(false)
	local forceClosing = event.forceClosing

	--由于切换网络时，会引发与服务器交互/接收不完整，引发奇异的Bug，
	--所以统一操作自动重联一次
	if forceClosing then
		self._hasAuth = false
		if self._switchNetwork then
			self._switchNetwork = false
			self:reconnectAuto()
		end
		return
	end

	if tapm.putKVS then
		tapm.putKVS("disconnect","kickoutByServer")
	end

	--需求：当APP从后台切回时，游戏服链接需要自动重联一次。
	if self._appEnterForeground then
		self._appEnterForeground = false
		self:reconnectAuto()
	else
		-- auth没成功的断开不触发自动重连
		if self._hasAuth then
			self:checkReconnctAuto()
		else
			executeFunc(self._authFailCB)
			self:clearAuthCB()
		end
	end

	self._hasAuth = false
end

----------------------- 同步查询 ---------------------
function Session:queryServerTime(cb)
	local params = {timestamp=math.floor(app:getTime()*1000 + 0.5)}
	self:request("pb_query.C2S_QueryServerTime",params,function(msg)
		--print("pb_query.C2S_QueryServerTime callback")
		local time = msg.timestamp--纳秒
		local seconds = time / 1000000000--秒
		self.serverTimeStamp = seconds
		self.queryBeginTime = app:getTime()
		if cb then
			cb()
		end
	end)
end

-- 获取服务器时区
function Session:getServerTimeZone()
	return self.timezone
end

-- 获取本地时区
function Session:getLocalTimeZone()
    if not self._localTimeZone then
    	-- os.time({year=1970,month=1,day=1,hour=0,min=0,sec=0}) 在某些时区，某些设备上会返回nil
        -- 这里是先获取day=2的时间戳，然后再减去一天的时间，再去计算时区

        -- local time = os.time({year=1970,month=1,day=2,hour=0,min=0,sec=0})
        -- time = time - 3600 * 24
        -- self._localTimeZone = -time / 3600
		-- 上面这种方法好像还是有问题，当时区改为 英国伦敦时(暂时只测出来这一个)，正常的应该是0时区，但结果却返回1时区，不知道是不是因为夏令时的问题。

        -- 以下方法是 用 当前时间 和 格林威治当前时间 差的小时数，来计算时区
        local now = os.time()
        self._localTimeZone = os.difftime(now, os.time(os.date("!*t", now)))/3600
    end

    return self._localTimeZone
end

-- 获取本地时区与服务器时区的偏移量
function Session:getTimeZoneOffset()
	return self:getLocalTimeZone() - self:getServerTimeZone()
end

-- 获得服务器时间
-- isTimeZone 考虑时区
function Session:getServerTime(isTimeZone)
	if not self.queryBeginTime or not self.serverTimeStamp then
		return nil
	end
	local duration = app:getTime()-self.queryBeginTime
	local serverTime = math.floor(self.serverTimeStamp + duration)
	if isTimeZone then
		-- 配置表里面的时间日期相关的参数，都是服务器使用的，客户端使用时需要考虑时区
		-- os.time 和 os.date 都和时区有关系，本地使用这两个方法获得服务器日期时，需要减去时区差值
		local offset = self:getTimeZoneOffset()
		serverTime = serverTime - offset * 3600
	end
	return serverTime
end

-- 服务器解析后的本地date，因为os.date已经考虑过时区了
function Session:getLocalDate()
	local time = self:getServerTime()
	if time then
		return os.date("*t", time)
	else
		return os.date("*t", os.time())
	end
end

-- 获取服务器当前的时间结构
function Session:getServerDate()
	local time = self:getServerTime(true)
	if time then
		return os.date("*t", time)
	else
		return os.date("*t", os.time())
	end
end

-- 获取服务器当前是星期几（0-6）
function Session:getServerWeekDate()
	local time = self:getServerTime(true)
	if time then
		return os.date("%w", time)
	else
		return os.date("%w", os.time())
	end
end

local secondsOfOneDay = 24*60*60

-- 判断时间time是否是今天
-- 今天 今天早上5点--明天早上5点
-- 昨天 昨天早上5点--今天早上5点
function Session:isToday(time)
	if not time then return true end

	local curTime = math.floor(self:getServerTime(true) or app:getTime())
	local startHour = tonumber(GD:getSystemPar("daystartsat", 5))
	local tab = os.date("*t", curTime)
	local curHour = tab.hour
	tab.hour = startHour
	tab.min = 0
	tab.sec = 0

	local todayStart = os.time(tab) -- 上次的刷新时间
	if curHour < startHour then
		-- 刷新时间之前
		todayStart = todayStart - secondsOfOneDay
	end
	local todayEnd = todayStart + secondsOfOneDay

	return time >= todayStart and time < todayEnd
end

function Session:_setBlock(b)
	if self._block ~= b then
		self._block = b
		if b then
			-- 转圈最多20s，20s后取消转圈
			self._blockCron = scheduler.newCron(function()
				release_print("Session block timeout")
				self._blockCron = nil
				self:_setBlock(false)
			end, 20)
		else
			if self._blockCron then
				scheduler.cancelCron(self._blockCron)
				self._blockCron = nil
			end
		end
		EM:notify(Session.Event_BlockingChanged)
	end
end

-- 获取开服天数
function Session:getOpenServerDays()
	--[[
	local days = 0
	if self.openStamp then
		local nowStamp = self:getServerTime()
		days = math.ceil( ( nowStamp - self.openStamp ) / secondsOfOneDay )
	end
	return days --]]
	return self.openDays
end

function Session:getMerageOpenDays()
	if not self.mergeDays or self.mergeDays == 0 then
		return self.openDays
	else
		return self.mergeDays
	end
end


--获得世界经验锁ID
function Session:getWorldExpLockId()
	return self.worldExpLock
end

-- 获取累计登录天数
function Session:getLoginDays()
	return self.loginDays
end

-- 是否是意见领袖
function Session:getIsKOL()
	return self.isKOL
end

-- 获得开服时间
function Session:getOpenServerDate()
	if self.openStamp then
		return os.date("*t", self.openStamp)
	end
end

function Session:getOpenServerTimeStamp()
	return self.openStamp or 0
end

function Session:isOpenServerTimeLargeDay(day)
	if self.openStamp then
		curTime = self:getServerTime() or os.time()
		if curTime - self.openStamp > day * 24 * 3600 then
			return true
		end
	end
	return false
end

return Session
